/**
 * 
 */
package monitor;

import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @author Robbie
 *
 */
public class Tracker extends Thread{
	
	private static int MAX_PACKET_SIZE = 1500;
	
	private PropertyChangeSupport listeners = new PropertyChangeSupport(this);
	
	private DatagramSocket myOutSocket;
	private DatagramSocket myInSocket;
	private ArrayList<DatagramPacket> packetQueue;
	private ArrayList<RemoteHost> nodeList;
	private PacketReceiver myReceiver;
	private Timer myTimer;
	private TimerTask myTask; 

	public Tracker(int listeningPort){
		try {
			myInSocket = new DatagramSocket(listeningPort);
			myOutSocket = new DatagramSocket();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		packetQueue = new ArrayList<DatagramPacket>();
		nodeList = new ArrayList<RemoteHost>();
		myReceiver = new PacketReceiver();
		myTimer = new Timer();
		setTimerTask();
	}
	
	private class PacketReceiver extends Thread{
		
		private boolean isInterrupted;
		private DatagramPacket myPacket;
		
		public PacketReceiver(){
			myPacket = new DatagramPacket(new byte[MAX_PACKET_SIZE],MAX_PACKET_SIZE);
		}
		
		public void run(){
			isInterrupted = false;
			while(!isInterrupted){
				try {
					myInSocket.receive(myPacket);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
//				System.out.println("Tracker received new packet");
				byte[] buffer = myPacket.getData();
				DatagramPacket temp = new DatagramPacket(buffer, buffer.length);
				temp.setAddress(myPacket.getAddress());
				temp.setPort(myPacket.getPort());
				myPacket = new DatagramPacket(new byte[MAX_PACKET_SIZE], MAX_PACKET_SIZE);
				synchronized(packetQueue){
					packetQueue.add(temp);
				}
			}
		}
		
		public void interruptData(){
			isInterrupted = true;
		}
	}
	
	public void run(){
		myReceiver.start();
		
		myTimer.scheduleAtFixedRate(myTask, 0, 500);	
	}
	
	private void setTimerTask(){
		myTask = new TimerTask(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				ArrayList<DatagramPacket> packets = new ArrayList<DatagramPacket>();
				synchronized(packetQueue){
					packets.addAll(packetQueue);
					packetQueue.clear();
				}
				
				if(packets.size()<1){
					return;
				}
				
				while(!packets.isEmpty()){
					DatagramPacket pkt = packets.remove(0);
					byte[] buffer = pkt.getData();
					InetAddress addr = pkt.getAddress();
					int index = 36;
					String uuid = new String(buffer, 0, 36);
					String name = new String(buffer, index ,8);
					int length = Integer.parseInt(new String(buffer,index+8,4),16);
					int port = Integer.parseInt(new String(buffer,index+12,length));
					RemoteHost newHost = new RemoteHost(addr,port,uuid);
					if(name.equals("NEW NODE")){
						boolean inList = sendNodeList(newHost);
						if(!inList){nodeList.add(newHost);}
					}else if(name.equals("GET LIST")){
						sendNodeList(newHost);
					}
				}
			}
			
		};
	}
	
	private boolean sendNodeList(RemoteHost newHost){
		boolean inList = false;
		ArrayList<RemoteHost> nodes = new ArrayList<RemoteHost>();
		synchronized(nodeList){
			nodes.addAll(nodeList);
		}
		if(nodes.isEmpty()){
			return false;
		}

		while(!nodes.isEmpty()){			
		
			DatagramPacket packet;
			
			RemoteHost h = nodes.remove(0);
			String uuid = h.getUUID().toString();
			String remote = h.getAddress().getHostAddress();
			
			if(h.equals(newHost)&&!inList){
				inList = true;
				continue;
			}
			
			if(h.equals(newHost.getAddress(), newHost.getPort())){
				synchronized(nodeList){
					nodeList.remove(h);
					nodeList.add(newHost);
				}
				continue;
			}
			
			remote = remote + ":" + Integer.toString(h.getPort());
			String length = Integer.toString(remote.length(), 16);
			if(length.length()==1){length = "000" + length;}
			else if(length.length()==2){length = "00" + length;}
			else if(length.length()==3){length = "0" + length;}
			String data = uuid + "NEW NODE" + length + remote;
			
			packet = new DatagramPacket(data.getBytes(), data.length());
			packet.setAddress(newHost.getAddress());
			packet.setPort(newHost.getPort());
			
			try {
				myOutSocket.send(packet);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return inList;
	}
	
	/**
	 * Adds a property change listener
	 * 
	 * @param l listener to add
	 */
	public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
	    listeners.addPropertyChangeListener(l);
	}
	
	/**
	 * Removes a property change listener
	 * 
	 * @param l listener to remove
	 */
	public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
	    listeners.removePropertyChangeListener(l);
	}
	
	protected void fireUpdate(PropertyChangeEvent evt) {
	    listeners.firePropertyChange(evt);
	}


	protected void scheduleUpdate(final PropertyChangeEvent evt) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				fireUpdate(evt);
			}
		});
	}
}
